package com.orange.core.service.user;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
import com.github.binarywang.wxpay.service.WxPayService;
import com.orange.core.config.wx.WxProperty;
import com.orange.core.mapper.floor.FloorMapper;
import com.orange.core.mapper.shop.ShopMapper;
import com.orange.core.mapper.user.UserCouponMapper;
import com.orange.core.mapper.user.UserMapper;
import com.orange.core.pojo.PageReqDto;
import com.orange.core.pojo.entity.coupon.Coupon;
import com.orange.core.pojo.entity.floor.Floor;
import com.orange.core.pojo.entity.shop.Shop;
import com.orange.core.pojo.entity.user.User;
import com.orange.core.pojo.entity.user.UserCoupon;
import com.orange.core.pojo.enums.*;
import com.orange.core.pojo.req.usercoupon.UserCouponPageReq;
import com.orange.core.pojo.req.usercoupon.UserCouponRefundApplyReq;
import com.orange.core.pojo.req.usercoupon.UserCouponRefundAuditReq;
import com.orange.core.pojo.req.writeoff.WriteOffSaveReq;
import com.orange.core.pojo.rsp.usercoupon.CouponOrderPayRsp;
import com.orange.core.pojo.rsp.usercoupon.CouponOrderRsp;
import com.orange.core.pojo.rsp.usercoupon.UserCouponExcel;
import com.orange.core.pojo.rsp.writeoff.WriteOffResultRsp;
import com.orange.core.service.coupon.CouponService;
import com.orange.core.service.writeoff.WriteOffSaveService;
import com.orange.core.util.*;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.RandomStringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class UserCouponService extends ServiceImpl<UserCouponMapper, UserCoupon> {

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserCouponService.class);

    private static final String COUPON_ORDER = "coupon_order:%s:%s";
    private static final String PAY_NOTIFY_LOCK = "pay_notify_lock";

    private final UserMapper userMapper;
    private final CouponService couponService;
    private final WriteOffSaveService writeOffSaveService;
    private final ShopMapper shopMapper;
    private final FloorMapper floorMapper;
    private final StringRedisTemplate redisTemplate;
    private final ObjectMapper objectMapper;
    private final RedissonClient redissonClient;
    private final WxPayService wxPayService;
    private final WxProperty wxProperty;

    public Page<UserCoupon> page(PageReqDto<UserCouponPageReq> req) {
        if (req.getCondition() == null) {
            req.setCondition(new UserCouponPageReq());
        }
        UserCouponPageReq condition = req.getCondition();
        Page<UserCoupon> page = this.lambdaQuery()
                .eq(condition.getUserId() != null, UserCoupon::getUserId, condition.getUserId())
                .eq(condition.getCouponId() != null, UserCoupon::getCouponId, condition.getCouponId())
                .eq(condition.getShopId() != null, UserCoupon::getShopId, condition.getShopId())
                .eq(UserCoupon::getPayStatus, EPayStatus.PAID.getValue())
                .eq(condition.getWriteOffStatus() != null, UserCoupon::getWriteOffStatus, condition.getWriteOffStatus())
                .eq(condition.getIsExpired() != null, UserCoupon::getIsExpired, condition.getIsExpired())
                .eq(condition.getRefundApplyStatus() != null, UserCoupon::getRefundApplyStatus, condition.getRefundApplyStatus())
                .in(CollUtil.isNotEmpty(condition.getRefundApplyStatuses()), UserCoupon::getRefundApplyStatus, condition.getRefundApplyStatuses())
                .eq(condition.getRefundStatus() != null, UserCoupon::getRefundStatus, condition.getRefundStatus())
                .orderByDesc(UserCoupon::getId)
                .page(req.toPage());
        List<UserCoupon> userCoupons = page.getRecords();
        if (CollUtil.isNotEmpty(userCoupons)) {
            List<Integer> writeOffByIds = userCoupons.stream().map(UserCoupon::getWriteOffBy).toList();
            Map<Integer, String> writeOffNameMap = userMapper.selectBatchIds(writeOffByIds)
                    .stream().collect(Collectors.toMap(User::getId, User::getNickname));

            List<Integer> auditByIds = userCoupons.stream().map(UserCoupon::getAuditBy).toList();
            Map<Integer, String> auditNameMap = userMapper.selectBatchIds(auditByIds)
                    .stream().collect(Collectors.toMap(User::getId, User::getNickname));

            List<Integer> couponIds = userCoupons.stream().map(UserCoupon::getCouponId).toList();
            Map<Integer, List<String>> couponTagsMap = couponService.listByIds(couponIds)
                    .stream().collect(Collectors.toMap(Coupon::getId, Coupon::getTags));

            for (UserCoupon userCoupon : userCoupons) {
                userCoupon.setWriteOffByNickname(writeOffNameMap.get(userCoupon.getWriteOffBy()));
                userCoupon.setAuditByNickname(auditNameMap.get(userCoupon.getAuditBy()));
                userCoupon.setTags(couponTagsMap.get(userCoupon.getCouponId()));
            }
        }
        return page;
    }

    public UserCoupon info(int id) {
        UserCoupon userCoupon = this.getById(id);
        if (userCoupon == null) {
            return null;
        }

        Shop shop = shopMapper.selectById(userCoupon.getShopId());
        Floor floor = floorMapper.selectById(shop.getFloorId());
        userCoupon.setShopNumber(shop.getShopNumber());
        userCoupon.setFloorId(shop.getFloorId());
        userCoupon.setFloorName(floor.getFloorName());
        return userCoupon;
    }

    @SneakyThrows
    public CouponOrderRsp generateOrder(int couponId) {
        Coupon coupon = couponService.lambdaQuery()
                .eq(Coupon::getId, couponId)
                .eq(Coupon::getShelf, 1)
                .eq(Coupon::getDel, 0)
                .one();
        Assert.notNull(coupon, "优惠券不存在");

        String orderNo;
        UserCoupon userCoupon;
        do {
            orderNo = RandomStringUtils.randomAlphanumeric(16).toUpperCase();
            userCoupon = this.lambdaQuery()
                    .eq(UserCoupon::getOrderNo, orderNo)
                    .one();
        } while (userCoupon != null);

        CouponOrderRsp couponOrder = CouponOrderRsp.builder()
                .couponId(couponId)
                .orderNo(orderNo)
                .amount(coupon.getPrice())
                .build();
        String key = String.format(COUPON_ORDER, PrincipalUtil.getId(), orderNo);
        redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(couponOrder));
        redisTemplate.expire(key, 30, TimeUnit.MINUTES);

        return couponOrder;
    }

    @SneakyThrows
    @Transactional
    public CouponOrderPayRsp pay(String orderNo) {
        String str = redisTemplate.opsForValue().get(String.format(COUPON_ORDER, PrincipalUtil.getId(), orderNo));
        Assert.notBlank(str, "订单发生变化，请重新下单");
        CouponOrderRsp couponOrder = objectMapper.readValue(str, CouponOrderRsp.class);

        // 扣减库存
        couponService.deductStock(couponOrder.getCouponId(), 1);

        Coupon coupon = couponService.getById(couponOrder.getCouponId());
        User user = userMapper.selectById(PrincipalUtil.getId());

        String writeOffNo;
        UserCoupon existUserCoupon;
        do {
            writeOffNo = Constants.COUPON_PREFIX + RandomStringUtils.randomAlphanumeric(11).toUpperCase();
            existUserCoupon = this.lambdaQuery()
                    .eq(UserCoupon::getWriteOffNo, writeOffNo)
                    .one();
        } while (existUserCoupon != null);

        // 生成用户优惠券
        UserCoupon userCoupon = generateUserCoupon(couponOrder, coupon, writeOffNo);
        this.save(userCoupon);

        int totalFee = couponOrder.getAmount().multiply(new BigDecimal("100")).intValue();

        WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
                .outTradeNo(couponOrder.getOrderNo())
                .totalFee(totalFee)
                .openid(user.getWxOpenId())
                .tradeType("JSAPI")
                .body(coupon.getTitle())
                .timeExpire(LocalDateTime.now().plusMinutes(30).format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")))
                .spbillCreateIp(IpUtil.getRemoteIp(RequestUtil.getRequest()))
                .notifyUrl(wxProperty.getCouponPayNotifyUrl())
                .build();
        WxPayMpOrderResult payOrderResult = wxPayService.createOrder(request);

        CouponOrderPayRsp couponOrderPayRsp = BeanUtil.copyProperties(payOrderResult, CouponOrderPayRsp.class);
        couponOrderPayRsp.setOrderId(userCoupon.getId());
        return couponOrderPayRsp;
    }

    private UserCoupon generateUserCoupon(CouponOrderRsp couponOrder, Coupon coupon, String writeOffNo) {
        Shop shop = shopMapper.selectById(coupon.getShopId());

        UserCoupon userCoupon = new UserCoupon();
        userCoupon.setUserId(PrincipalUtil.getId());
        userCoupon.setCouponId(couponOrder.getCouponId());
        userCoupon.setShopId(coupon.getShopId());
        userCoupon.setOrderNo(couponOrder.getOrderNo());
        userCoupon.setTitle(coupon.getTitle());
        userCoupon.setCouponValue(coupon.getCouponValue());
        userCoupon.setUseThreshold(coupon.getUseThreshold());
        userCoupon.setShopName(shop.getName());
        userCoupon.setShopAddress(shop.getAddress());
        userCoupon.setIconUrl(StrUtil.isNotBlank(coupon.getIconUrl()) ? coupon.getIconUrl() : shop.getLogo());
        userCoupon.setEffectStart(coupon.getEffectStart());
        userCoupon.setEffectEnd(coupon.getEffectEnd());
        userCoupon.setUsageNote(coupon.getUsageNote());
        userCoupon.setPayStatus(EPayStatus.UNPAID.getValue());
        userCoupon.setWriteOffNo(writeOffNo);
        userCoupon.setWriteOffStatus(EWriteOffStatus.PENDING.getValue());
        userCoupon.setRefundStatus(ERefundStatus.NOT_REFUNDED.getValue());
        userCoupon.setRefundApplyStatus(ERefundApplyStatus.NOT_APPLIED.getValue());
        return userCoupon;
    }

    @SneakyThrows
    public String wechatPayNotify(String notifyResult) {
        WxPayOrderNotifyResult result = wxPayService.parseOrderNotifyResult(notifyResult);
        result.checkResult(wxPayService, result.getSignType(), true);
        RLock lock = redissonClient.getFairLock(PAY_NOTIFY_LOCK);
        boolean locked = false;
        try {
            lock.tryLock(2, 2, TimeUnit.SECONDS);
            locked = true;

            String orderNo = result.getOutTradeNo();
            UserCoupon userCoupon = this.lambdaQuery()
                    .eq(UserCoupon::getOrderNo, orderNo)
                    .one();
            if (userCoupon == null) {
                return WxPayNotifyResponse.fail("订单不存在");
            }

            UserCoupon update = new UserCoupon();
            update.setId(userCoupon.getId());
            update.setPayStatus(EPayStatus.PAID.getValue());
            update.setPaidAmount(new BigDecimal(BaseWxPayResult.fenToYuan(result.getTotalFee())).stripTrailingZeros());
            update.setTransactionId(result.getTransactionId());
            update.setRefundStatus(ERefundStatus.NOT_REFUNDED.getValue());
            this.updateById(update);

            return WxPayNotifyResponse.success("订单已处理");
        } catch (IllegalStateException e) {
            log.error("加锁失败", e);
            throw e;
        } catch (Exception e) {
            log.error("方法执行失败", e);
            throw e;
        } finally {
            if (locked) {
                lock.unlock();
            }
        }
    }

    /**
     * 核销
     */
    @Transactional
    public WriteOffResultRsp writeOff(String writeOffNo) {
        UserCoupon userCoupon = this.lambdaQuery()
                .eq(UserCoupon::getPayStatus, EPayStatus.PAID.getValue())
                .eq(UserCoupon::getWriteOffNo, writeOffNo)
                .one();
        Assert.notNull(userCoupon, "优惠券不存在");
        Assert.equals(userCoupon.getWriteOffStatus(), EWriteOffStatus.PENDING.getValue(), "优惠券已核销");
        Assert.equals(userCoupon.getIsExpired(), false, "优惠券已过期，无法核销");
        // 检查是否已退款
        Assert.equals(userCoupon.getRefundStatus(), ERefundStatus.NOT_REFUNDED.getValue(), "优惠券已退款，无法核销");

        // 检查是否有待审核的退款申请
        Assert.isFalse(userCoupon.getRefundApplyStatus().equals(ERefundApplyStatus.PENDING.getValue()), "优惠券有待审核的退款申请，无法核销");

        User merchant = userMapper.selectById(PrincipalUtil.getId());
        Assert.equals(userCoupon.getShopId(), merchant.getShopId(), "无权核销该优惠券");

        this.lambdaUpdate()
                .set(UserCoupon::getWriteOffStatus, EWriteOffStatus.VERIFIED.getValue())
                .set(UserCoupon::getWriteOffTime, LocalDateTime.now())
                .set(UserCoupon::getWriteOffBy, PrincipalUtil.getId())
                .set(UserCoupon::getUseTime, LocalDateTime.now())
                .eq(UserCoupon::getId, userCoupon.getId())
                .update();

        User user = userMapper.selectById(PrincipalUtil.getId());

        writeOffSaveService.writeOff(WriteOffSaveReq.builder()
                .businessId(userCoupon.getId())
                .businessType(EWriteOffType.COUPON.getValue())
                .userId(userCoupon.getUserId())
                .shopId(user.getShopId())
                .build());

        return WriteOffResultRsp.builder()
                .id(userCoupon.getId())
                .writeOffType(EWriteOffType.COUPON.getValue())
                .writeOffNo(userCoupon.getWriteOffNo())
                .writeOffTime(userCoupon.getWriteOffTime())
                .title(userCoupon.getTitle())
                .effectStart(userCoupon.getEffectStart())
                .effectEnd(userCoupon.getEffectEnd())
                .amount(userCoupon.getPaidAmount())
                .build();
    }

    @SneakyThrows
    public void export(UserCouponPageReq condition, HttpServletResponse response) {
        List<UserCoupon> userCoupons = this.lambdaQuery()
                .eq(condition.getUserId() != null, UserCoupon::getUserId, condition.getUserId())
                .eq(condition.getCouponId() != null, UserCoupon::getCouponId, condition.getCouponId())
                .eq(condition.getShopId() != null, UserCoupon::getShopId, condition.getShopId())
                .eq(UserCoupon::getPayStatus, EPayStatus.PAID.getValue())
                .eq(condition.getWriteOffStatus() != null, UserCoupon::getWriteOffStatus, condition.getWriteOffStatus())
                .eq(condition.getIsExpired() != null, UserCoupon::getIsExpired, condition.getIsExpired())
                .eq(condition.getRefundApplyStatus() != null, UserCoupon::getRefundApplyStatus, condition.getRefundApplyStatus())
                .eq(condition.getRefundStatus() != null, UserCoupon::getRefundStatus, condition.getRefundStatus())
                .orderByDesc(UserCoupon::getId)
                .list();
        if (CollUtil.isNotEmpty(userCoupons)) {
            List<Integer> shopIds = userCoupons.stream().map(UserCoupon::getShopId).toList();
            List<Shop> shops = shopMapper.selectBatchIds(shopIds);
            Map<Integer, Shop> shopMap = shops.stream().collect(Collectors.toMap(Shop::getId, Function.identity()));
            List<Integer> floorIds = shops.stream().map(Shop::getFloorId).toList();
            Map<Integer, String> floorMap = floorMapper.selectBatchIds(floorIds)
                    .stream().collect(Collectors.toMap(Floor::getId, Floor::getFloorName));
            List<Integer> writeOffByIds = userCoupons.stream().map(UserCoupon::getWriteOffBy)
                    .filter(Objects::nonNull).toList();
            Map<Integer, String> writeOffByMap = userMapper.selectBatchIds(writeOffByIds)
                    .stream().collect(Collectors.toMap(User::getId, User::getNickname));

            List<Integer> auditByIds = userCoupons.stream().map(UserCoupon::getAuditBy)
                    .filter(Objects::nonNull).toList();
            Map<Integer, String> auditByMap = userMapper.selectBatchIds(auditByIds)
                    .stream().collect(Collectors.toMap(User::getId, User::getNickname));

            List<UserCouponExcel> results = new ArrayList<>();
            for (UserCoupon userCoupon : userCoupons) {
                UserCouponExcel result = BeanUtil.copyProperties(userCoupon, UserCouponExcel.class);
                Shop shop = shopMap.get(result.getShopId());
                if (shop != null) {
                    result.setShopNumber(shop.getShopNumber());
                    result.setFloorName(floorMap.get(shop.getFloorId()));
                }
                result.setEffectStart(userCoupon.getEffectStart().format(Constants.DEFAULT_DATE_FORMATTER));
                result.setEffectEnd(userCoupon.getEffectEnd().format(Constants.DEFAULT_DATE_FORMATTER));
                result.setPayStatus(EPayStatus.getLabel(userCoupon.getPayStatus()));
                result.setWriteOffStatus(EWriteOffStatus.getLabel(userCoupon.getWriteOffStatus()));
                if (userCoupon.getWriteOffTime() != null) {
                    result.setWriteOffTime(userCoupon.getWriteOffTime().format(Constants.DEFAULT_DATE_TIME_FORMATTER));
                }
                if (userCoupon.getWriteOffBy() != null) {
                    result.setWriteOffByNickname(writeOffByMap.get(result.getWriteOffBy()));
                }
                result.setIsExpired(userCoupon.getIsExpired() ? "未过期" : "已过期");
                if (result.getUseTime() != null) {
                    result.setUseTime(userCoupon.getUseTime().format(Constants.DEFAULT_DATE_TIME_FORMATTER));
                }
                result.setCreateTime(userCoupon.getCreateTime().format(Constants.DEFAULT_DATE_TIME_FORMATTER));

                // 退款申请相关字段
                if (userCoupon.getRefundApplyStatus() != null) {
                    result.setRefundApplyStatus(ERefundApplyStatus.getLabel(userCoupon.getRefundApplyStatus()));
                }
                result.setRefundReason(userCoupon.getRefundReason());
                if (userCoupon.getRefundApplyTime() != null) {
                    result.setRefundApplyTime(userCoupon.getRefundApplyTime().format(Constants.DEFAULT_DATE_TIME_FORMATTER));
                }

                // 审核人相关字段
                if (userCoupon.getAuditBy() != null) {
                    result.setAuditByNickname(auditByMap.get(userCoupon.getAuditBy()));
                }
                if (userCoupon.getAuditTime() != null) {
                    result.setAuditTime(userCoupon.getAuditTime().format(Constants.DEFAULT_DATE_TIME_FORMATTER));
                }
                result.setAuditRemark(userCoupon.getAuditRemark());

                // 退款执行相关字段
                if (userCoupon.getRefundStatus() != null) {
                    result.setRefundStatus(ERefundStatus.getLabel(userCoupon.getRefundStatus()));
                }
                if (userCoupon.getRefundTime() != null) {
                    result.setRefundTime(userCoupon.getRefundTime().format(Constants.DEFAULT_DATE_TIME_FORMATTER));
                }
                results.add(result);
            }
            ExcelUtil.write(results, UserCouponExcel.class, response, "用户优惠券导出");
        }
    }

    /**
     * 申请退款
     * 注意：过期但未核销的优惠券也可以申请退款
     */
    @Transactional
    public void applyRefund(UserCouponRefundApplyReq req) {
        UserCoupon userCoupon = this.getById(req.getId());
        Assert.notNull(userCoupon, "用户优惠券不存在");

        // 检查订单是否属于当前用户
        Assert.equals(userCoupon.getUserId(), PrincipalUtil.getId(), "无权操作该优惠券");

        // 检查是否已支付
        Assert.equals(userCoupon.getPayStatus(), EPayStatus.PAID.getValue(), "优惠券未支付，无法申请退款");

        // 检查是否已核销
        Assert.equals(userCoupon.getWriteOffStatus(), EWriteOffStatus.PENDING.getValue(), "优惠券已核销，无法申请退款");

        // 检查是否已退款
        Assert.equals(userCoupon.getRefundStatus(), ERefundStatus.NOT_REFUNDED.getValue(), "优惠券已退款");

        // 检查是否已申请退款（只有未申请或被拒绝的才能申请）
        Assert.isTrue(userCoupon.getRefundApplyStatus().equals(ERefundApplyStatus.NOT_APPLIED.getValue()) ||
                        userCoupon.getRefundApplyStatus().equals(ERefundApplyStatus.REJECTED.getValue()) ||
                        userCoupon.getRefundApplyStatus().equals(ERefundApplyStatus.CANCELED.getValue()),
                "已申请退款，请勿重复申请");

        // 检查是否已核销
        Assert.equals(userCoupon.getWriteOffStatus(), EWriteOffStatus.PENDING.getValue(), "优惠券已核销，无法申请退款");

        // 更新退款申请状态
        this.lambdaUpdate()
                .set(UserCoupon::getRefundApplyStatus, ERefundApplyStatus.PENDING.getValue())
                .set(UserCoupon::getRefundReason, req.getRefundReason())
                .set(UserCoupon::getRefundRemark, req.getRefundRemark())
                .set(UserCoupon::getRefundApplyTime, LocalDateTime.now())
                .eq(UserCoupon::getId, userCoupon.getId())
                .update();
    }

    /**
     * 审核退款申请
     */
    @SneakyThrows
    @Transactional
    public void auditRefund(UserCouponRefundAuditReq req) {
        UserCoupon userCoupon = this.getById(req.getId());
        Assert.notNull(userCoupon, "用户优惠券不存在");

        // 检查是否已申请退款
        Assert.equals(userCoupon.getRefundApplyStatus(), ERefundApplyStatus.PENDING.getValue(), "退款申请状态异常");

        // 检查是否已退款
        Assert.equals(userCoupon.getRefundStatus(), ERefundStatus.NOT_REFUNDED.getValue(), "优惠券已退款");

        // 更新审核状态
        this.lambdaUpdate()
                .set(UserCoupon::getRefundApplyStatus, req.getAuditStatus())
                .set(UserCoupon::getAuditBy, PrincipalUtil.getId())
                .set(UserCoupon::getAuditTime, LocalDateTime.now())
                .set(UserCoupon::getAuditRemark, req.getAuditRemark())
                .eq(UserCoupon::getId, userCoupon.getId())
                .update();

        // 如果审核通过，执行退款
        if (req.getAuditStatus().equals(ERefundApplyStatus.APPROVED.getValue())) {
            // 调用微信退款接口
            int refundFee = userCoupon.getPaidAmount().multiply(new BigDecimal("100")).intValue();

            WxPayRefundRequest refundRequest = WxPayRefundRequest.newBuilder()
                    .outTradeNo(userCoupon.getOrderNo())
                    .outRefundNo("RF" + userCoupon.getOrderNo())
                    .totalFee(refundFee)
                    .refundFee(refundFee)
                    .refundDesc("优惠券退款")
                    .notifyUrl(wxProperty.getCouponRefundNotifyUrl())
                    .build();

            wxPayService.refund(refundRequest);
        }
    }

    /**
     * 微信退款回调
     */
    @SneakyThrows
    public String wechatRefundNotify(String notifyResult) {
        log.info("微信退款回调：{}", notifyResult);

        try {
            // 解析退款回调数据
            WxPayRefundNotifyResult result = wxPayService.parseRefundNotifyResult(notifyResult);

            RLock lock = redissonClient.getFairLock("refund_notify_lock");
            boolean locked = false;
            try {
                lock.tryLock(2, 2, TimeUnit.SECONDS);
                locked = true;

                String outTradeNo = result.getReqInfo().getOutTradeNo();
                String outRefundNo = result.getReqInfo().getOutRefundNo();

                // 根据订单号查找用户优惠券
                UserCoupon userCoupon = this.lambdaQuery()
                        .eq(UserCoupon::getOrderNo, outTradeNo)
                        .one();

                if (userCoupon == null) {
                    return WxPayNotifyResponse.fail("订单不存在");
                }

                // 检查退款状态
                if ("SUCCESS".equals(result.getReqInfo().getRefundStatus())) {
                    // 退款成功
                    this.lambdaUpdate()
                            .set(UserCoupon::getRefundStatus, ERefundStatus.REFUNDED.getValue())
                            .set(UserCoupon::getRefundTime, LocalDateTime.now())
                            .set(UserCoupon::getRefundTransactionId, result.getReqInfo().getRefundId())
                            .eq(UserCoupon::getId, userCoupon.getId())
                            .update();

                    // 退还库存
                    couponService.addStock(userCoupon.getCouponId(), 1);

                    log.info("退款成功，订单号：{}，退款单号：{}", outTradeNo, outRefundNo);
                } else if ("FAIL".equals(result.getReqInfo().getRefundStatus())) {
                    // 退款失败
                    this.lambdaUpdate()
                            .set(UserCoupon::getRefundApplyStatus, ERefundApplyStatus.REJECTED.getValue())
                            .set(UserCoupon::getAuditRemark, "微信退款失败：" + result.getReqInfo().getRefundStatus())
                            .eq(UserCoupon::getId, userCoupon.getId())
                            .update();

                    log.error("退款失败，订单号：{}，退款单号：{}，状态：{}", outTradeNo, outRefundNo, result.getReqInfo().getRefundStatus());
                }

                return WxPayNotifyResponse.success("退款回调已处理");
            } catch (IllegalStateException e) {
                log.error("加锁失败", e);
                throw e;
            } finally {
                if (locked) {
                    lock.unlock();
                }
            }
        } catch (Exception e) {
            log.error("退款回调处理失败", e);
            return WxPayNotifyResponse.fail("退款回调处理失败：" + e.getMessage());
        }
    }
}
